using GameFrame.Deterministic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace GameFrame
{
    [StructLayout(LayoutKind.Explicit)]
    public struct PhysicsCollider2D : IComponent
    {
        public const int SIZE = 80;
        [FieldOffset(0)]
        private byte _isTrigger;
        [FieldOffset(1)]
        private byte _disabled;
        [FieldOffset(2)]
        private short _padding;
        [FieldOffset(4)]
        private int _layer;
        [FieldOffset(8)]
        private AssetRefPhysicsMaterial _material;
        [FieldOffset(16)]
        public Shape2D Shape;

        public int Layer
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this._layer;
            [MethodImpl(MethodImplOptions.AggressiveInlining)] set => this._layer = value;
        }

        public bool IsTrigger
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this._isTrigger == (byte) 1;
            [MethodImpl(MethodImplOptions.AggressiveInlining)] set => this._isTrigger = value ? (byte) 1 : (byte) 0;
        }

        public AssetRefPhysicsMaterial Material
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this._material;
            [MethodImpl(MethodImplOptions.AggressiveInlining)] set => this._material = value;
        }

        /// <summary>
        /// If this component should be considered by the Physics Engine.
        /// <remarks>
        /// Disabling a collider has immediate effect: queries will not find it, nor will collisions be solved nor callbacks called.
        /// Re-enabling, on the other hand, is deferred to the next Physics Update.
        /// </remarks>
        /// </summary>
        public bool Enabled
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this._disabled == (byte) 0;
            [MethodImpl(MethodImplOptions.AggressiveInlining)] set => this._disabled = value ? (byte) 0 : (byte) 1;
        }

        public static PhysicsCollider2D Create(
            FrameBase f,
            Shape2D shape,
            PhysicsMaterial material = null,
            bool isTrigger = false,
            int layer = 0)
        {
            PhysicsCollider2D physicsCollider2D = new PhysicsCollider2D()
            {
                IsTrigger = isTrigger,
                Layer = layer,
                _material = new AssetRefPhysicsMaterial()
                {
                    Id = material != null ? material.Guid : (AssetGuid) 1L
                }
            };
            if (shape._type == Shape2DType.Compound)
            {
                physicsCollider2D.Shape = Shape2D.CreatePersistentCompound();
                physicsCollider2D.Shape.Compound.CopyFrom(f, ref shape);
            }
            else
                physicsCollider2D.Shape = shape;
            return physicsCollider2D;
        }

        public static unsafe void Serialize(void* ptr, FrameSerializer serializer)
        {
            PhysicsCollider2D* physicsCollider2DPtr = (PhysicsCollider2D*) ptr;
            serializer.Stream.Serialize(&physicsCollider2DPtr->_disabled);
            serializer.Stream.Serialize(&physicsCollider2DPtr->_isTrigger);
            serializer.Stream.Serialize(&physicsCollider2DPtr->_padding);
            serializer.Stream.Serialize(&physicsCollider2DPtr->_layer);
            AssetRefPhysicsMaterial.Serialize((void*) &physicsCollider2DPtr->_material, (IDeterministicFrameSerializer) serializer);
            Shape2D.Serialize((void*) &physicsCollider2DPtr->Shape, serializer);
        }

        internal static unsafe void OnAdded(FrameBase f, EntityRef entity, void* component)
        {
        }

        internal static unsafe void OnRemoved(FrameBase f, EntityRef entity, void* component)
        {
            PhysicsCollider2D* physicsCollider2DPtr = (PhysicsCollider2D*) component;
            if (physicsCollider2DPtr->Shape._type != Shape2DType.Compound)
                return;
            physicsCollider2DPtr->Shape.Compound.FreePersistent(f);
        }
    }
}